package com.lifang.framework.beans.factory.support;

import cn.hutool.core.util.StrUtil;
import com.lifang.framework.context.annotation.ClassPathBeanDefinitionScanner;
import com.lifang.framework.beans.BeansException;
import com.lifang.framework.beans.factory.PropertyValue;
import com.lifang.framework.beans.factory.config.BeanDefinitionRegistry;
import com.lifang.framework.core.io.DefaultResourceLoader;
import com.lifang.framework.core.io.Resource;
import com.lifang.framework.core.io.ResourceLoader;
import com.lifang.framework.util.ClassUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * xml 资源加载类
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    /**
     * 使用默认加载策略
     */
    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry, new DefaultResourceLoader());
    }

    /**
     * 使用传入的资源加载策略
     */
    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        super(registry, resourceLoader);
    }

    /**
     * 加载指定资源
     */
    @Override
    public void loadBeanDefinitions(Resource resource) throws BeansException {
        try {
            try (InputStream inputStream = resource.getInputStream()) {
                doLoadBeanDefinitions(inputStream);
            }
        } catch (IOException | ClassNotFoundException | DocumentException e) {
            throw new BeansException("IOException parsing XML document from " + resource, e);
        }
    }

    /**
     * 加载指定资源集合
     */
    @Override
    public void loadBeanDefinitions(Resource... resources) throws BeansException {
        for (Resource resource : resources) {
            loadBeanDefinitions(resource);
        }
    }

    /**
     * 加载特定资源 如 classpath:spring.xml
     */
    @Override
    public void loadBeanDefinitions(String location) throws BeansException {
        ResourceLoader resourceLoader = getResourceLoader();
        Resource resource = resourceLoader.getResource(location);
        loadBeanDefinitions(resource);
    }

    @Override
    public void loadBeanDefinitions(String... locations) throws BeansException {
        for (String location : locations) {
            loadBeanDefinitions(location);
        }
    }

    /**
     * 执行加载（读取）xml
     */
    protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException, DocumentException {
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        Element rootElement = document.getRootElement();

        Element componentScan = rootElement.element("component-scan");
        if (componentScan != null) {
            String scanPath = componentScan.attributeValue("base-package");
            if (StrUtil.isEmpty(scanPath)) {
                throw new BeansException("The value of base-package attribute can not be empty or null");
            }
            scanPackage(scanPath);
        }

        List<Element> beans = rootElement.elements("bean");
        for (Element beanElement : beans) {

            String id = beanElement.attributeValue("id");
            String name = beanElement.attributeValue("name");
            String className = beanElement.attributeValue("class");
            String initMethod = beanElement.attributeValue("init-method");
            String destroyMethodName = beanElement.attributeValue("destroy-method");
            String beanScope = beanElement.attributeValue("scope");

            Class<?> clazz = ClassUtils.forName(className);
            String beanName = StrUtil.isNotEmpty(id) ? id : name;
            if (StrUtil.isEmpty(beanName)) {
                beanName = StrUtil.lowerFirst(clazz.getSimpleName());
            }

            BeanDefinition beanDefinition = new BeanDefinition(clazz);
            beanDefinition.setInitMethodName(initMethod);
            beanDefinition.setDestroyMethodName(destroyMethodName);
            if (StrUtil.isNotEmpty(beanScope)) {
                beanDefinition.setScope(beanScope);
            }

            List<Element> property = beanElement.elements("property");
            for (Element propertyElement : property) {

                String attrName = propertyElement.attributeValue("name");
                String attrValue = propertyElement.attributeValue("value");
                String attrRef = propertyElement.attributeValue("ref");

                Object value = StrUtil.isNotEmpty(attrRef) ? new BeanReference(attrRef) : attrValue;
                PropertyValue propertyValue = new PropertyValue(attrName, value);
                beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
            }
            if (getRegistry().containsBeanDefinition(beanName)) {
                throw new BeansException("Duplicate beanName [" + beanName + "] is not allowed");
            }
            getRegistry().registerBeanDefinition(beanName, beanDefinition);
        }
    }

    private void scanPackage(String scanPath) {
        String[] basePackages = StrUtil.splitToArray(scanPath, ',');
        ClassPathBeanDefinitionScanner classPathBeanDefinitionScanner = new ClassPathBeanDefinitionScanner(getRegistry());
        classPathBeanDefinitionScanner.doScan(basePackages);
    }

}
